﻿using Furion;
using Furion.DatabaseAccessor;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;

namespace YFurion.Controllers
{
    /// <summary>
    /// 工作单元拦截器
    /// </summary>
    public sealed class YUnitOfWorkFilter : IAsyncActionFilter, IOrderedFilter
    {
        /// <summary>
        /// MiniProfiler 分类名
        /// </summary>
        private const string MiniProfilerCategory = "transaction";

        /// <summary>
        /// 过滤器排序
        /// </summary>
        public const int FilterOrder = 9999;

        /// <summary>
        /// 排序属性
        /// </summary>
        public int Order => FilterOrder;

        /// <summary>
        /// 数据库上下文池
        /// </summary>
        private readonly IDbContextPool _dbContextPool;

        /// <summary>
        /// 数据库上下文事务集合
        /// </summary>
        private ICollection<IDbContextTransaction> _dbContextTransactions;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="dbContextPool"></param>
        public YUnitOfWorkFilter(IDbContextPool dbContextPool)
        {
            _dbContextPool = dbContextPool;
            _dbContextTransactions = new List<IDbContextTransaction>();
        }

        /// <summary>
        /// 拦截请求
        /// </summary>
        /// <param name="context">动作方法上下文</param>
        /// <param name="next">中间件委托</param>
        /// <returns></returns>
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            // 获取动作描述器
            ControllerActionDescriptor actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
            // 动作执行后上下文
            ActionExecutedContext resultContext;

            // 判断是否贴有工作单元特性
            if (!actionDescriptor.MethodInfo.IsDefined(typeof(YUnitOfWorkAttribute)))
            {
                // 调用方法
                resultContext = await next();

                // 判断是否异常
                if (resultContext.Exception == null)
                {
                    _dbContextPool.SavePoolNow();
                }

                _dbContextPool.CloseAll();

                return;
            }

            // 打印事务开始消息
            App.PrintToMiniProfiler(MiniProfilerCategory, "Beginning");

            // 开启事务
            BeginDbContextTransactions();

            // 调用方法
            resultContext = await next();

            // 判断是否异常
            if (resultContext.Exception == null)
            {
                try
                {
                    // 将所有数据库上下文修改 SaveChanges();
                    int hasChangesCount = _dbContextPool.SavePoolNow();

                    // 提交共享事务
                    CommitTransactions();

                    // 打印事务提交消息
                    App.PrintToMiniProfiler(MiniProfilerCategory, "Completed", $"Transaction Completed! Has {hasChangesCount} DbContext Changes.");
                }
                catch
                {
                    // 回滚事务
                    RollbackTransactions();

                    // 打印事务回滚消息
                    App.PrintToMiniProfiler(MiniProfilerCategory, "Rollback", isError: true);

                    throw;
                }
                finally
                {
                    DisposeTransactions();
                }
            }
            else
            {
                // 回滚事务
                RollbackTransactions();
                DisposeTransactions();

                // 打印事务回滚消息
                App.PrintToMiniProfiler(MiniProfilerCategory, "Rollback", isError: true);
            }

            _dbContextPool.CloseAll();
        }

        private void BeginDbContextTransactions()
        {
            foreach (DbContext dbContext in _dbContextPool.GetDbContexts().Values)
            {
                if (!HasReadOnlyDbContext(dbContext.GetType()))
                {
                    _dbContextTransactions.Add(dbContext.Database.BeginTransaction());
                }
            }
        }

        private bool HasReadOnlyDbContext(Type type)
        {
            bool has = false;
            Type baseType = type.BaseType;
            if (baseType != null)
            {
                has = baseType.Name.Equals("ReadOnlyDbContext`2");
                if (!has)
                {
                    has = HasReadOnlyDbContext(baseType);
                }
            }
            return has;
        }

        private void CommitTransactions()
        {
            foreach (IDbContextTransaction dbContextTransaction in _dbContextTransactions)
            {
                dbContextTransaction?.Commit();
            }
        }

        private void RollbackTransactions()
        {
            foreach (IDbContextTransaction dbContextTransaction in _dbContextTransactions)
            {
                dbContextTransaction?.Rollback();
            }
        }

        private void DisposeTransactions()
        {
            foreach (IDbContextTransaction dbContextTransaction in _dbContextTransactions)
            {
                dbContextTransaction?.Dispose();
            }
        }
    }
}